/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/** 
 * \file
 * \brief Name mapping table for Nameservice.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#include "nametable.h"
#include "owndebug.h"
#include "ownutils.h"
#include "idmap.h"
#include "strmap.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>

//*****************************************************************************

static struct SNameTable
{
  long int total_names;
  TIDMap nodeIdToName;
  TStrMap nameToNodeId;
} nametable;
//*****************************************************************************

static inline void destroyNametableEntry(TNameTableEntry *entry)
{
  free(entry);
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void nametableInit(const int expected_max_size)
{
  dPrint(ODTEST,"Called");
  // Clear and start setting up
  memset(&nametable, 0x00, sizeof(struct SNameTable));

  // Allocate memory for expected max size
  const int size_guess = MAX(expected_max_size / 10, 10);
  idMapInit(&nametable.nodeIdToName,  size_guess);
  strMapInit(&nametable.nameToNodeId, size_guess);

  // Register cleanup func
  //atexit(nametableDeinit);
}
//*****************************************************************************

void nametableDeinit()
{
  dPrint(ODTEST,"Called");
  idMapDeinit(&nametable.nodeIdToName);
  strMapDeinit(&nametable.nameToNodeId);
  memset(&nametable, 0x00, sizeof(struct SNameTable));
}
//*****************************************************************************

inline void nametableUpdateTimestamp(TNameTableEntry *entry)
{
  const time_t v = time(&entry->timestamp_s);
  if (v == ((time_t) -1)) {
    dPrintLE(ODERROR,"Call to time() failed!");
  }
}
//*****************************************************************************

int nametableInsertEntry(const char *name, const uint32_t nodeid, 
                         const uint32_t max_age_s, TNameTableEntry const ** existing_entry)
{
  int result = KNametableResultError;

  if (strlen(name) < 32) {

    // Attempt to search by both values; first by ID, then by name.
    const TNameTableEntry *entry = nametableGetEntryByNodeId(nodeid);
    if (!entry) entry = nametableGetEntryByName(name);
    
    // If STILL no entry found, given values are unique and may be added to database.
    if (!entry) {
      TNameTableEntry *entry = (TNameTableEntry *)malloc(sizeof(TNameTableEntry));
      assert(entry != NULL);
      memset(entry, 0x00, sizeof(TNameTableEntry));
      
      // Construct the new entry
      strncpy(entry->name, name, sizeof(entry->name));
      entry->name[sizeof(entry->name)-1] = '\0';
      entry->nodeid = nodeid;
      nametableUpdateTimestamp(entry);
      entry->max_age_s = max_age_s;
      
      // Insert!(Return values match with OK value, so no checks required)
      result = idMapAddEntry(&nametable.nodeIdToName, nodeid, entry);
      result |= strMapAddEntry(&nametable.nameToNodeId, name, entry);
      
    } else {
      if (existing_entry) *existing_entry = entry;
      result = KNametableResultConflict;

    }
  }

  return result;
}
//*****************************************************************************

int nametableDeleteEntryByName(const char *name)
{
  int result = KNametableResultError;
  const TNameTableEntry *entry = nametableGetEntryByName(name);
  if (entry) {
    result = nametableDeleteEntryByEntryPtr((TNameTableEntry *)entry);
  }
  return result;
}
//*****************************************************************************

int nametableDeleteEntryByNodeId(const uint32_t nodeid)
{
  int result = KNametableResultError;
  const TNameTableEntry *entry = nametableGetEntryByNodeId(nodeid);
  if (entry) {
    result = nametableDeleteEntryByEntryPtr((TNameTableEntry *)entry);
  }
  return result;
}
//*****************************************************************************

int nametableDeleteEntryByEntryPtr(TNameTableEntry *entryPtr)
{
  int result = KNametableResultError;
  assert(entryPtr != NULL);

  result = idMapDeleteEntry(&nametable.nodeIdToName, entryPtr->nodeid);
  result |= strMapDeleteEntry(&nametable.nameToNodeId, entryPtr->name);
  destroyNametableEntry(entryPtr);

  return result ? -1 : 0;
}
//*****************************************************************************

const TNameTableEntry *nametableGetEntryByName(const char *name)
{
  const TStrMapEntry *entry = strMapGetEntry(&nametable.nameToNodeId, name);
  if (entry) {
    return entry->entry;
  } else return NULL;
}
//*****************************************************************************

const TNameTableEntry *nametableGetEntryByNodeId(const uint32_t nodeid)
{
  const TIDMapEntry *entry = idMapGetEntry(&nametable.nodeIdToName, nodeid);
  if (entry) {
    return entry->entry;
  } else return NULL;
}
//*****************************************************************************

inline int nametableIsExpiredEntry(const TNameTableEntry *entry)
{
  return nametableEntryGetTimeToExpiration(entry) == 0;
}
//*****************************************************************************

inline long int nametableEntryGetTimeToExpiration(const TNameTableEntry *entry)
{
  long int result = KNametableResultError;
  time_t ts_s;
  
  const time_t v = time(&ts_s);
  if (v == ((time_t) -1)) {
    dPrintLE(ODERROR,"Call to time() failed!");
    
  } else if (entry->max_age_s) {
    // Compare only seconds; faster.
    result = (entry->timestamp_s + entry->max_age_s) - ts_s;
    if (result < 0) result = 0; // Truncate to zero if entry is older.

  }

  return result; 
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
#ifdef COMPILE_UNIT_TEST
/**
gcc -g3 -Wall -I. -I../../ -I../../protocol -I../../../utils -I../../clientlib -DLINUX_OS -DUSE_SELECT_IO -DCOMPILE_UNIT_TEST nametable.c ../../../utils/own*.c -lpthread -lrt ../../idmap.c ../../strmap.c -o nametable.unit
 */
#include <stdio.h>
int main(void)
{
  long int i;
  ownTime_ms_t begin;
  ownTime_ms_delta_t took;

  debugInit();
  debugSetGlobalDebugLvl(ODINFO);

  const int limit = 1000000;
  nametableInit(limit);



  //////// Insert
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    char name[32];
    sprintf(name,"blaaa%lu", i);
    assert(nametableInsertEntry(name, i, i, NULL) == KNametableResultOK);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld Inserts completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/limit);


  //////// Query by name
  begin = ownTime_get_ms();
  long int expired = 0;
  for(i=0; i < limit; ++i) {
    char name[32];
    sprintf(name,"blaaa%lu", i);
    const TNameTableEntry *entry = nametableGetEntryByName(name);
    assert(entry);
    if (nametableIsExpiredEntry(entry)) expired++;
    assert(strcmp(entry->name, name) == 0);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld GetByName:s completed, operation took %lu ms, %.2f us per entry (%ld expired)\n", 
         i, (unsigned long)took, (1000.0*took)/limit, expired);
  

  //////// Delete by name
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    char name[32];
    sprintf(name,"blaaa%lu", i);
    const int value = nametableDeleteEntryByName(name);
    assert(value == KNametableResultOK);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld DeleteEntryByName:s completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/limit);


  //////// Insert
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    char name[32];
    sprintf(name,"blaaa%lu", i);
    assert(nametableInsertEntry(name, i, 1000, NULL) == KNametableResultOK);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld Inserts completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/limit);


  //////// Query by ID
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    const TNameTableEntry *entry = nametableGetEntryByNodeId(i);
    assert(entry);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld GetByNodeId:s completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/limit);


  //////// Delete by ID
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    const int value = nametableDeleteEntryByNodeId(i);
    assert(value == KNametableResultOK);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld DeleteEntryByNodeId:s completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/limit);


  nametableDeinit();

  return 0;
}
#endif
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
